<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>XXX</title>
    <script src="../libs/system.js"></script>
    <script>
        System.import('../libs/cc.js');
    </script>
    <script>


        // 世界坐标系里计算
        // 1. 表示物体? mesh.
        // 2. 表示人眼
        // 3.
        //
        // 一些临时变量


        const DevWidth = 320 / 1;
        const DevHeight = 240 / 1;

        function calPlaneABC(camera) {
            let c_zdir = cc.v3();
            cc.Vec3.subtract(c_zdir, camera.pos, camera.target);
            cc.Vec3.normalize(c_zdir, c_zdir);

            let c_xdir = cc.v3();
            cc.Vec3.cross(c_xdir, cc.Vec3.UP, c_zdir);

            let c_ydir = cc.v3();
            cc.Vec3.cross(c_ydir, c_zdir, c_xdir);

            let plane = new cc.geometry.plane(0, 0, 0, 0);
            return cc.geometry.plane.fromNormalAndPoint(plane, c_ydir, camera.pos);
        }

        let graphics = {
            canvas: null,
            ctx: null,
            imgData: null,
            initMemory() {
                let canvas = this.canvas = document.createElement('canvas');
                canvas.width = canvas.clientWidth = DevWidth;
                canvas.height = canvas.clientHeight = DevHeight;
                canvas.style.background = '#000';
                document.body.appendChild(canvas);

                let ctx = this.ctx = canvas.getContext('2d');
                this.imgData = ctx.createImageData(DevWidth, DevHeight);
            },
            clear() {
                // this.ctx.clearRect(0,0,DevWidth,DevHeight);
                this.imgData.data.fill(0, 0, this.imgData.data.length);
            },
            swap() {
                this.ctx.putImageData(this.imgData, 0, 0);
            },

            drawPoint(x, y, color) {
                let num = (x + y * DevWidth) * 4;
                this.imgData.data[num] = color.r;
                this.imgData.data[num + 1] = color.g;
                this.imgData.data[num + 2] = color.b;
                this.imgData.data[num + 3] = color.a;
            }
        };

        let obj;
        let gMin, gMax;
        let camera;
        let lightPos;
        window.onload = function () {

            graphics.initMemory();

            /**
             *  相机所在的点为C,相机的方向为 CD
             *  相机方向和投影平面的交点为D
             *  D在投影平面内垂直于CD的那条线  和上方的交点为A 下面的交点为B  左边为E  右边为F
             */
            camera = {
                pos: cc.v3(0, 1, 5),
                target: cc.v3(0, 0, 0),
                fov: 60,
                aspect: DevWidth / DevHeight,
                near: 1,
            };

            lightPos = cc.v3(0,10,10);

            let planeMesh =
                new BallMeshInfo(2,20);
                 // new BoxMeshInfo({min: [-1, -1, -1], max: [1, 1, 1]});
            obj = new Obj(planeMesh);
            obj.rotateY(30);

            // new BallMeshInfo(1,10);


            // 求 A B 坐标, 计算出每条射线
            // 先求 ABC 平面
            // const plane = calPlaneABC(camera);

            //  求D点坐标
            let d = cc.v3();
            let cd_dir = cc.v3();
            let ncd_dir = cc.v3();
            cc.Vec3.subtract(cd_dir, camera.target, camera.pos);
            cc.Vec3.normalize(ncd_dir, cd_dir);
            cc.Vec3.scaleAndAdd(d, camera.pos, ncd_dir, camera.near);


            let tan = Math.tan(camera.fov / 2 * Math.PI / 180);
            let vm = cc.mat4();
            let vmInv = cc.mat4();
            cc.Mat4.lookAt(vm, camera.pos, camera.target, cc.Vec3.UP);
            cc.Mat4.invert(vmInv, vm);

            let vm_a = cc.v3(0, tan * camera.near, -camera.near);
            let a = cc.v3();
            cc.Vec3.transformMat4(a, vm_a, vmInv);


            // b
            let vm_b = cc.v3(0, -tan * camera.near, -camera.near);
            let b = cc.v3();
            cc.Vec3.transformMat4(b, vm_b, vmInv);


            // e  (-x)    f(+x)
            let vm_e = cc.v3(-tan * camera.near * camera.aspect, 0, -camera.near);
            let vm_f = cc.v3(tan * camera.near * camera.aspect, 0, -camera.near);
            let e = cc.v3(), f = cc.v3();
            cc.Vec3.transformMat4(e, vm_e, vmInv);
            cc.Vec3.transformMat4(f, vm_f, vmInv);


            // 把 AB 分为 DevHeight
            //    EF 分为 DevWidth
            // 发射射线
            // let disAB = 2 * tan * camera.near;
            // let disEF = 2 * tan * camera.near * camera.aspect;
            let vm_min = cc.v3(vm_e.x, vm_b.y, -camera.near);
            let vm_max = cc.v3(vm_f.x, vm_a.y, -camera.near);
            let min = cc.v3(), max = cc.v3();
            cc.Vec3.transformMat4(min, vm_min, vmInv);
            cc.Vec3.transformMat4(max, vm_max, vmInv);


            //
            console.log('a:', a);
            console.log('b:', b);
            console.log('e:', e);
            console.log('f:', f);
            console.log('min:', min);
            console.log('max:', max);

            gMin = min;
            gMax = max;

            // let dx = disEF / DevWidth;
            // let dy = disAB / DevHeight;

            // test_intersectin();

            // for test
            // ad^2 + cd^2 = ac^2
            // console.log('a:',a);
            // console.log(test_acd(a,camera.pos,d));
            // console.log(test_acd(b,camera.pos,d));
            //
            // console.log('plane:',plane);

            loop();
        };

        function loop() {
            graphics.clear();

            console.time('RAY');
            let min = gMin;
            let max = gMax;
            let p = cc.v3();
            let tmp_v3 = cc.v3();

            let lightToP = cc.v3();
            let tmp_p = cc.v3();

            let high = cc.Color.GREEN;
            let low = cc.color(0,~~(0.2 * 255),0,255);


            for (let i = 0; i < DevHeight; ++i) {
                let t = i / DevHeight;
                for (let j = 0; j < DevWidth; ++j) {
                    let s = j / DevWidth;
                    let x = cc.math.lerp(min.x, max.x, s);
                    let y = cc.math.lerp(min.y, max.y, t);
                    let z = min.z;
                    cc.Vec3.set(p, x, y, z);
                    cc.Vec3.subtract(tmp_v3, p, camera.pos);
                    cc.Vec3.normalize(tmp_v3,tmp_v3);

                    // 放这条射线出去 然后求交
                    let ray = cc.geometry.ray.create(
                        camera.pos.x, camera.pos.y, camera.pos.z,
                        tmp_v3.x, tmp_v3.y, tmp_v3.z
                    );

                    // if(calIntersections(ray, j, i)){
                    //     continue;
                    // }
                    let t1 = cal_ray_with_mesh(ray,obj);
                    if(t1 > 0){
                        cc.Vec3.scaleAndAdd(tmp_p,camera.pos,ray.d,t);
                        cc.Vec3.subtract(lightToP,tmp_p,lightPos);
                        let dist = cc.Vec3.len(lightToP);
                        cc.Vec3.normalize(lightToP,lightToP);

                        let ray2 = cc.geometry.ray.create(
                            lightPos.x, lightPos.y, lightPos.z,
                            lightToP.x, lightToP.y, lightToP.z
                        );
                        let t2 = cal_ray_with_mesh(ray2,obj);
                        if(t2 >= dist - 0.1){
                            // 被照亮
                            graphics.drawPoint(j, i, high);
                        }else{
                            graphics.drawPoint(j, i, low);
                        }
                        continue;
                    }
                }
            }
            console.timeEnd('RAY');

            // for(let i = 0; i < 100; ++i){
            //     let x = cc.math.randomRangeInt(0,DevWidth - 1);
            //     let y = cc.math.randomRangeInt(0,DevHeight - 1);
            //     graphics.drawPoint(x,y,cc.Color.GREEN);
            // }
            graphics.swap();

            // requestAnimationFrame(loop);
        }

        function test_acd(a, c, d) {
            let ad = cc.Vec3.squaredDistance(a, d);
            let cd = cc.Vec3.squaredDistance(c, d);
            let ac = cc.Vec3.squaredDistance(a, c);
            return [ad + cd, ac];
        }

        //计算ray 和 整个场景求交
        function calIntersections(ray, x, y) {
            // 暂时只有 planeMesh
            let vertexs = obj.getWorldVertexs();
            let indices = obj.mesh.indices;
            let faceLen = Math.floor(indices.length / 3);

            let tmpTri = cc.geometry.triangle.create();
            let t_a = cc.v3(), t_b = cc.v3(), t_c = cc.v3();

            // 计算一个简单的光照
            let t_p = cc.v3();
            for (let i = 0; i < faceLen; ++i) {
                let i0 = indices[3 * i];
                let i1 = indices[3 * i + 1];
                let i2 = indices[3 * i + 2];

                cc.Vec3.set(t_a, vertexs[3 * i0], vertexs[3 * i0 + 1], vertexs[3 * i0 + 2]);
                cc.Vec3.set(t_b, vertexs[3 * i1], vertexs[3 * i1 + 1], vertexs[3 * i1 + 2]);
                cc.Vec3.set(t_c, vertexs[3 * i2], vertexs[3 * i2 + 1], vertexs[3 * i2 + 2]);

                cc.geometry.triangle.fromPoints(tmpTri, t_a, t_b, t_c);
                let t = cc.geometry.intersect.ray_triangle(ray, tmpTri, false);
                if (t > 0) {

                    // 求出交点
                    cc.Vec3.scaleAndAdd(t_p,camera.pos,ray.d,t);

                    // 判断p能否被照亮?
                    // p到光源间是否有其他东西



                    graphics.drawPoint(x, y, cc.Color.GREEN);
                    return true;
                }
            }
            return false;
        }

        function intersectionWithLight(p) {
            let light_to_p = cc.v3();
            cc.Vec3.subtract(light_to_p,p,lightPos);
            let len = cc.Vec3.length(light_to_p);

        }

        function cal_ray_with_mesh(ray,obj) {
            let vertexs = obj.getWorldVertexs();
            let indices = obj.mesh.indices;
            let faceLen = Math.floor(indices.length / 3);

            let tmpTri = cc.geometry.triangle.create();
            let t_a = cc.v3(), t_b = cc.v3(), t_c = cc.v3();

            // 计算一个简单的光照
            for (let i = 0; i < faceLen; ++i) {
                let i0 = indices[3 * i];
                let i1 = indices[3 * i + 1];
                let i2 = indices[3 * i + 2];

                cc.Vec3.set(t_a, vertexs[3 * i0], vertexs[3 * i0 + 1], vertexs[3 * i0 + 2]);
                cc.Vec3.set(t_b, vertexs[3 * i1], vertexs[3 * i1 + 1], vertexs[3 * i1 + 2]);
                cc.Vec3.set(t_c, vertexs[3 * i2], vertexs[3 * i2 + 1], vertexs[3 * i2 + 2]);

                cc.geometry.triangle.fromPoints(tmpTri, t_a, t_b, t_c);
                let t = cc.geometry.intersect.ray_triangle(ray, tmpTri, false);
                if (t > 0) {
                    return t;
                }
            }
            return 0;
        }

        function test_intersectin() {

            let a = cc.v3(0, 2, 0);
            let b = cc.v3(-2, -1, 0);
            let c = cc.v3(2, -1, 0);

            let ray = cc.geometry.ray.create(0, -10, 1, 0, 0, -1);
            // let t_a = cc.v3(), t_b = cc.v3(), t_c = cc.v3();
            let tmpTri = cc.geometry.triangle.create();
            cc.geometry.triangle.fromPoints(tmpTri, a, b, c);
            let t = cc.geometry.intersect.ray_triangle(ray, tmpTri, true);
            console.log('t:', t);
        }


        //-----------------------------------
        //
        class PlaneMeshInfo {
            constructor(width = 10, height = 10, widthSegments = 10, lengthSegments = 10) {

                let plane = new cc.primitives.plane({
                    includeNormal: true,
                    includeUV: false,
                    width: width,
                    height: height,
                    widthSegments: widthSegments,
                    lengthSegments: lengthSegments
                });

                this.vertexs = plane.positions;
                this.indices = plane.indices;
                this.normals = plane.normals;
                this.min = [plane.minPos.x, plane.minPos.y, plane.minPos.z];
                this.max = [plane.maxPos.x, plane.maxPos.y, plane.maxPos.z];
            }
        }

        class BallMeshInfo {
            constructor(r, segments = 32) {
                this.radius = r;

                let ball = new cc.primitives.sphere(r, {segments: segments});
                this.vertexs = ball.positions;
                this.indices = ball.indices;
                this.normals = ball.normals;
                this.min = [ball.minPos.x, ball.minPos.y, ball.minPos.z];
                this.max = [ball.maxPos.x, ball.maxPos.y, ball.maxPos.z];
            }
        }


        class BoxMeshInfo {
            constructor(meshInfo) {
                this.min = meshInfo.min.map(v => {
                    return v
                });
                this.max = meshInfo.max.map(v => {
                    return v
                });

                // 顶点信息
                let x = this.max[0] - this.min[0];
                let y = this.max[1] - this.min[1];
                let z = this.max[2] - this.min[2];

                this.vertexs = [
                    // 上面4个点
                    -x / 2, y / 2, -z / 2, // 0
                    x / 2, y / 2, -z / 2, // 1
                    x / 2, y / 2, z / 2, // 2
                    -x / 2, y / 2, z / 2, // 3

                    // 下面4个点
                    -x / 2, -y / 2, -z / 2, // 4
                    x / 2, -y / 2, -z / 2, // 5
                    x / 2, -y / 2, z / 2, // 6
                    -x / 2, -y / 2, z / 2, // 7
                ];
                this.indices = [
                    0, 3, 2, // +y
                    0, 2, 1,

                    3, 7, 6, // +z
                    3, 6, 2,

                    7, 4, 5, // -y
                    7, 5, 6,

                    0, 4, 7, // -x
                    0, 7, 3,

                    2, 6, 5, // +x
                    2, 5, 1,

                    1, 5, 4, // -z
                    1, 4, 0,
                ];
                this.normals = this.calNormals(this.vertexs,this.indices);
            }
            calNormals(vertexs,indices){
                let normals = new Array(vertexs.length);
                // 手工计算顶点法线
                let all = {};
                let faceCount = Math.floor(indices.length / 3);
                let v01 = cc.v3();
                let v12 = cc.v3();
                let v0 = cc.v3();
                let v1 = cc.v3();
                let v2 = cc.v3();
                let tmpNormal = cc.v3();
                for(let i = 0; i < faceCount; ++i){
                    let i0 = indices[3 * i];
                    let i1 = indices[3 * i + 1];
                    let i2 = indices[3 * i + 2];
                    cc.Vec3.set(v0,vertexs[3 * i0],vertexs[3 * i0 + 1],vertexs[3 * i0 + 2]);
                    cc.Vec3.set(v1,vertexs[3 * i1],vertexs[3 * i1 + 1],vertexs[3 * i1 + 2]);
                    cc.Vec3.set(v2,vertexs[3 * i2],vertexs[3 * i2 + 1],vertexs[3 * i2 + 2]);

                    cc.Vec3.subtract(v01,v1,v0);
                    cc.Vec3.subtract(v12,v2,v1);
                    cc.Vec3.cross(tmpNormal,v01,v12);
                    cc.Vec3.normalize(tmpNormal,tmpNormal);
                    if(!all[i0]){
                        all[i0] = [];
                    }
                    all[i0].push([tmpNormal.x,tmpNormal.y,tmpNormal.z]);

                    if(!all[i1]){
                        all[i1] = [];
                    }
                    all[i1].push([tmpNormal.x,tmpNormal.y,tmpNormal.z]);

                    if(!all[i2]){
                        all[i2] = [];
                    }
                    all[i2].push([tmpNormal.x,tmpNormal.y,tmpNormal.z]);
                }

                //  求平均值
                for(let k in all){
                    let idx = parseInt(k);
                    let arr = all[k];
                    let dlen = 1 / (arr.length || 1);
                    let x = 0,y = 0,z = 0;
                    arr.forEach(item=>{
                        x += item[0];
                        y += item[1];
                        z += item[2];
                    });

                    cc.Vec3.set(tmpNormal,x * dlen, y * dlen,z * dlen);
                    cc.Vec3.normalize(tmpNormal,tmpNormal);

                    normals[3 * idx] = tmpNormal.x;
                    normals[3 * idx + 1] = tmpNormal.y;
                    normals[3 * idx + 2] = tmpNormal.z;
                }

                return normals;
            }
        }

        class Obj {
            constructor(mesh) {
                this.mesh = mesh;
                this.worldMat = cc.mat4();
            }

            rotateY(degress) {
                let tmp = cc.mat4();
                cc.Mat4.fromYRotation(tmp, degress * Math.PI / 180);
                cc.Mat4.multiply(this.worldMat, this.worldMat, tmp);
            }

            getWorldVertexs(){
                let vertexs = this.mesh.vertexs;
                let len = Math.floor(vertexs.length / 3);
                let v = cc.v3();
                let finalVertexs = [];
                let worldMat = this.worldMat;
                for(let i = 0; i < len; ++i){
                    cc.Vec3.set(v,vertexs[3 * i],vertexs[3 * i + 1],vertexs[3 * i + 2]);
                    cc.Vec3.transformMat4(v,v,worldMat);
                    finalVertexs.push(v.x,v.y,v.z);
                }
                return finalVertexs;
            }
        }

    </script>
</head>
<body>

</body>
</html>
